package cn.micai.base.lock;

import redis.clients.jedis.Jedis;
import java.util.Collections;

/**
 * 描述：分布式锁  基于缓存（Redis）实现分布式锁   使用setnx实现
 * <p>
 * 针对分布式锁的实现，目前比较常用的有以下几种方案
 * 1.基于数据库实现分布式锁
 *      基于数据库实现分布式锁的思想：额外创建一张表，用来做分布式锁，某一字段用来存方法名称，为了方法名称相同而引起冲突，可以用包名+类名+方法名的形式来防止发生，该字段设置非空索引、唯一索引。在执行分布式的时候，争抢资源前需要对目标方法插入该表，如果插入成功，获取锁成功，可以执行相关方法；如果插入失败，获取锁失败，尝试执行获取锁或者放弃执行目标方法。这就是基于数据库实现分布式锁的实现思想。
 * 2.基于缓存(Redis，Memcached)实现分布式锁
 *      Redis实现分布式锁有两种方案：
 *      1、使用setnx实现。
 *      2、使用官方推荐的Redisson、RedLock实现。
 * 3.基于Zookeeper实现分布式锁
 *
 * 本文重点讲解基于缓存（Redis）实现分布式锁
 *
 * @author: 赵新国
 * @date: 2018/4/10 14:58
 */
public class RedisDistributedLockSetnx {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 释放分布式锁结果
     */
    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 尝试获取分布式锁
     * 如果获取锁成功：return true，执行相关操作
     * 如果获取锁失败：return false，尝试执行或放弃执行
     * @param jedis Redis客户端
     * @param lockKey 锁,eg:lock:test
     * @param requestId 请求标识,eg:UUID
     * @param expireTime 超期时间,单位:毫秒,eg:5000(5s)
     * @return boolean 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, long expireTime) {
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        return LOCK_SUCCESS.equals(result);
    }

    /**
     * 释放分布式锁
     * 可以不主动释放，锁有一个定时，当然，也可以主动释放，提供此方法就是为了主动释放
     * return true 释放成功
     * return false 释放失败
     * @param jedis Redis客户端
     * @param lockKey 锁,eg:lock:test
     * @param requestId 请求标识,eg:UUID
     * @return boolean 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        return RELEASE_SUCCESS.equals(result);
    }

    /**
     * 方法注释已经很详细了，在执行目标方法之前，调用tryGetDistributedLock()方法，return true说明获得锁，执行相关代码；return false说明获取锁失败，尝试执行获取或放弃执行目标方法。流程图如下：
     */

}
